#include <QJsonObject>
#include <QJsonArray>

#include <osg/Geometry>

#include <Geom_BSplineCurve.hxx>
#include <Geom2d_BSplineCurve.hxx>
#include <Geom2d_TrimmedCurve.hxx>
#include <GeomAdaptor_Curve.hxx>
#include <GCPnts_TangentialDeflection.hxx>
#include <GCPnts_UniformDeflection.hxx>
#include <CPnts_AbscissaPoint.hxx>
#include <Geom2dConvert.hxx>
#include <Geom2dAPI_ProjectPointOnCurve.hxx>
#include <Geom2d_OffsetCurve.hxx>

#include "Spline2D.h"
#include "JsonReader.h"
#include "GLSLPrograms.h"
#include "QtJson.h"
#include "OsgUtil.h"
#include "UCS.h"
#include "Spline3D.h"
#include "Scene.h"

// 类似于createEdgeProgram, 除了z值处理之外
osg::ref_ptr<osg::Program> createEdgeProgram2();

struct Spline2DPrivate
{
	Spline2DPrivate()
		: m_rep3D(new osg::Geometry())
	{
		m_rep3D->setNodeMask(D2CurveMask);
		osg::ref_ptr<osg::Program> program = createEdgeProgram2();
		auto ss = m_rep3D->getOrCreateStateSet();
		ss->setAttributeAndModes(program, osg::StateAttribute::ON);
	}
	osg::ref_ptr<osg::Geometry> m_rep3D;
	std::vector<osg::Vec3d> m_points; // 曲线上的点，缓存数据

	// 样条线的定义
	int m_Flag;
	int m_Degree;
	std::vector<double> m_Knots;
	std::vector<double> m_Weights;
	std::vector<osg::Vec3d> m_ControlPoints;

	osg::Vec4 m_color = { 1, 1, 1, 1 };
	bool m_selected = false;
};

Spline2D::Spline2D(INode* parent)
	: Ent2DCurve(parent)
	, d_ptr(new Spline2DPrivate)
{
}

void Spline2D::setFlag(int flag)
{
	if (d_ptr->m_Flag != flag)
	{
		d_ptr->m_Flag = flag;
addFlags(UPDATE_GEOMETRY);
	}
}

void Spline2D::setDegree(int degree)
{
	if (d_ptr->m_Degree != degree)
	{
		d_ptr->m_Degree = degree;
		addFlags(UPDATE_GEOMETRY);
	}
}

void Spline2D::setCtrlPoints(const std::vector<osg::Vec3d> &pos)
{
	if (d_ptr->m_ControlPoints != pos)
	{
		d_ptr->m_ControlPoints = pos;
		addFlags(UPDATE_GEOMETRY);
	}
}

void Spline2D::setKnots(const std::vector<double>& knots)
{
	if (d_ptr->m_Knots != knots)
	{
		d_ptr->m_Knots = knots;
		addFlags(UPDATE_GEOMETRY);
	}
}

void Spline2D::setWeights(const std::vector<double>& weights)
{
	if (d_ptr->m_Weights != weights)
	{
		d_ptr->m_Weights = weights;
		addFlags(UPDATE_GEOMETRY);
	}
}

void Spline2D::setColor(const osg::Vec4 &color)
{
	if (d_ptr->m_color != color)
	{
		d_ptr->m_color = color;
		addFlags(UPDATE_GEOMETRY);
	}
}

int Spline2D::getFlag() const
{
	return d_ptr->m_Flag;
}

int Spline2D::getDegree() const
{
	return d_ptr->m_Degree;
}

const std::vector<osg::Vec3d>& Spline2D::getCtrlPoints() const
{
	return d_ptr->m_ControlPoints;
}

const std::vector<double>& Spline2D::getKnots() const
{
	return d_ptr->m_Knots;
}

const std::vector<double>& Spline2D::getWeights() const
{
	return d_ptr->m_Weights;
}

const osg::Vec4& Spline2D::color() const {
	return d_ptr->m_color;
}

std::vector<osg::Vec3d> convert(const TColgp_Array1OfPnt& points);
TColgp_Array1OfPnt convert(const std::vector<osg::Vec3d>& points);
std::vector<double> convert(const TColStd_Array1OfReal& weights);
TColStd_Array1OfReal convert(const std::vector<double>& weights);
std::vector<int> convert(const TColStd_Array1OfInteger& mults);
TColStd_Array1OfInteger convert(const std::vector<int>& mults);
void toOCCTKnots(const std::vector<double>& acadKnots, std::vector<double>& knots, std::vector<int>& mults);
void toACADKnots(const std::vector<double>& knots, const std::vector<int>& mults, std::vector<double>& acadKnots);

std::vector<osg::Vec3d> convert2d(const TColgp_Array1OfPnt2d& points)
{
	std::vector<osg::Vec3d> pointsV;
	for (int i = points.Lower(); i <= points.Upper(); ++i)
	{
		const auto& pt = points(i);
		pointsV.push_back(osg::Vec3d(pt.X(), pt.Y(), 0));
	}
	return pointsV;
}

TColgp_Array1OfPnt2d convert2d(const std::vector<osg::Vec3d>& points)
{
	TColgp_Array1OfPnt2d pointsA(1, points.size());
	for (int i = 0; i < points.size(); ++i)
	{
		const auto& cp = points[i];
		pointsA.SetValue(i + 1, gp_Pnt2d(cp.x(), cp.y()));
	}
	return pointsA;
}

Handle(Geom_BSplineCurve) getCurve(int m_Flag,
	int m_Degree,
	const std::vector<double>& m_Knots,
	std::vector<double>& m_Weights,
	const std::vector<osg::Vec3d>& m_ControlPoints
);

void fromCurve(Handle(Geom2d_BSplineCurve) curve,
	int& m_Flag,
	int& m_Degree,
	std::vector<double>& m_Knots,
	std::vector<double>& m_Weights,
	std::vector<osg::Vec3d>& m_ControlPoints)
{
	m_Flag = curve->IsPeriodic() ? 2 : 0;
	m_Flag |= 8; // 平面
	m_Degree = curve->Degree();
	auto poles = curve->Poles();
	m_ControlPoints = convert2d(poles);
	auto weights = curve->Weights();
	if (weights)
		m_Weights = convert(*weights);
	else
		m_Weights.resize(m_ControlPoints.size(), 1.0);
	auto knots = curve->Knots();
	auto mults = curve->Multiplicities();
	m_Knots.clear();
	toACADKnots(convert(knots), convert(mults), m_Knots);
}

void Spline2D::update3D()
{
	if (flags())
	{
		// 计算m_points
		int cpNum = d_ptr->m_ControlPoints.size();
		// 均匀折线化
		d_ptr->m_points.clear();
		try
		{
			Handle(Geom_BSplineCurve) curve = getCurve(d_ptr->m_Flag, d_ptr->m_Degree, d_ptr->m_Knots,
				d_ptr->m_Weights, d_ptr->m_ControlPoints);
			GCPnts_TangentialDeflection myAlgo2;
			double aDeflection = 3.0 / 180 * M_PI;
			GeomAdaptor_Curve adaptedCurve(curve);
			double len = 0;
			for (int i = 0; i < cpNum - 1; ++i)
			{
				const auto& det = d_ptr->m_ControlPoints[i + 1] - d_ptr->m_ControlPoints[i];
				len += sqrt(det.x() * det.x() + det.y() * det.y() + det.z() * det.z());
			}
			double Deflection = len / 200.0;
			myAlgo2.Initialize(adaptedCurve, aDeflection, Deflection);
			//if (myAlgo2.IsDone())
			{
				int nbr = myAlgo2.NbPoints();
				double param;
				gp_Pnt pt;
				for (int i = 1; i <= nbr; i++)
				{
					param = myAlgo2.Parameter(i);
					pt = myAlgo2.Value(i);
					//pt = pt.Transformed(L.Transformation());
					d_ptr->m_points.push_back(osg::Vec3d(pt.X(), pt.Y(), pt.Z()));
				}
			}
		}
		catch (Standard_Failure&)
		{
			d_ptr->m_points.clear();
		}

		// 组装OSG图形对象
		const osg::Vec4f& selectedColor = osg::Vec4f(0.f, 0.8f, 0.f, 1.f);
		auto va = new osg::Vec3Array();
		for (auto pt : d_ptr->m_points)
		{
			va->push_back(pt);
		}
		d_ptr->m_rep3D->setVertexArray(va);

		osg::ref_ptr<osg::Vec4Array> ca = new osg::Vec4Array();
	
		if (d_ptr->m_selected)
			ca->push_back(selectedColor);
		else
			ca->push_back(d_ptr->m_color);
		
		d_ptr->m_rep3D->setColorArray(ca.get(), osg::Array::BIND_OVERALL);

		d_ptr->m_rep3D->getPrimitiveSetList().clear();
		d_ptr->m_rep3D->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, d_ptr->m_points.size()));

		d_ptr->m_rep3D->dirtyGLObjects();
		removeFlags(0xFFFFFFFF);
	}
}

osg::Node * Spline2D::getRep3D() const
{
	return d_ptr->m_rep3D.get();
}

void Spline2D::setSelected(bool val)
{
	if (d_ptr->m_selected != val)
	{
		d_ptr->m_selected = val;
		const osg::Vec4f& selectedColor = osg::Vec4f(0.f, 0.8f, 0.f, 1.f);
		auto ca = dynamic_cast<osg::Vec4Array*>(d_ptr->m_rep3D->getColorArray());
		assert(ca);
		if (ca)
		{
			ca->clear();
			if (d_ptr->m_selected)
				ca->push_back(selectedColor);
			else
				ca->push_back(d_ptr->m_color);
			d_ptr->m_rep3D->setColorArray(ca, osg::Array::BIND_OVERALL);
			ca->dirty();
			d_ptr->m_rep3D->dirtyGLObjects();
		}
	}
}

void Spline2D::save(QJsonObject & json) const
{
	Ent2DCurve::save(json);

	json["color"] = saveVec4f(d_ptr->m_color);

	json["flag"] = d_ptr->m_Flag;
	json["degree"] = d_ptr->m_Degree;
	json["knots"] = saveDoubles(d_ptr->m_Knots);
	json["weights"] = saveDoubles(d_ptr->m_Weights);
	QJsonArray poles;
	for (const auto& pt : d_ptr->m_ControlPoints)
	{
		poles.append(saveVec2d({ pt.x(),pt.y() }));
	}
	json["poles"] = poles;
}

void Spline2D::load(const QJsonObject& json, JsonReader& reader)
{
	Ent2DCurve::load(json, reader);

	d_ptr->m_color = loadVec4f(json["color"].toArray());

	d_ptr->m_Flag = json["flag"].toInt();
	d_ptr->m_Degree = json["degree"].toInt();
	d_ptr->m_Knots = loadDoubles(json["knots"].toArray());
	d_ptr->m_Weights = loadDoubles(json["weights"].toArray());
	QJsonArray poles = json["poles"].toArray();
	for (int i = 0; i < poles.count(); i++)
	{
		const auto& pole = poles.at(i);
		d_ptr->m_ControlPoints.push_back({ loadVec2d(pole.toArray()),0 });
	}
}

std::vector<osg::Vec3d> Spline2D::handles() const
{
	return d_ptr->m_ControlPoints;
}

void Spline2D::move(const osg::Vec3d& offset)
{
	for (int i = 0; i< d_ptr->m_ControlPoints.size();i++)
	{
		d_ptr->m_ControlPoints[i] = d_ptr->m_ControlPoints[i] + offset;
	}
	addFlags(UPDATE_GEOMETRY);
}

void Spline2D::scale(const osg::Vec3d & center, double scale)
{
	for (auto& pt : d_ptr->m_points)
	{
		pt = center + (pt - center) * scale;
	}
}

void Spline2D::mirror(const osg::Vec3d & start, const osg::Vec3d & end)
{
	for (int i = 0; i < d_ptr->m_ControlPoints.size(); i++)
	{
		d_ptr->m_ControlPoints[i] = mirror2D(start, end, d_ptr->m_ControlPoints[i]);
	}
	addFlags(UPDATE_GEOMETRY);
}

void Spline2D::stretch(int index, const osg::Vec3d &offset)
{
	d_ptr->m_ControlPoints[index] = d_ptr->m_ControlPoints[index] + offset;
	addFlags(UPDATE_GEOMETRY);
}

std::shared_ptr<Ent> Spline2D::clone() const
{
	auto spline = std::make_shared<Spline2D>(getParent());
	spline->copy(*this);
	return spline;
}

void Spline2D::copy(const Ent& ent)
{
	auto spline = dynamic_cast<const Spline2D*>(&ent);
	if (spline)
	{
		d_ptr->m_color = spline->color();
		d_ptr->m_Flag = spline->getFlag();
		d_ptr->m_Degree = spline->getDegree();
		d_ptr->m_ControlPoints = spline->getCtrlPoints();
		d_ptr->m_Knots = spline->getKnots();
		d_ptr->m_Weights = spline->getWeights();
		addFlags(UPDATE_GEOMETRY);
	}
}

void Spline2D::rotate(const double angle, const osg::Vec2d & basePoint)
{
	for (int i = 0; i < d_ptr->m_ControlPoints.size(); i++)
	{
		rotate2D(basePoint.x(), basePoint.y(), angle, d_ptr->m_ControlPoints[i]);
	}
	addFlags(UPDATE_GEOMETRY);
}

TColgp_Array1OfPnt2d convert2D(const std::vector<osg::Vec3d>& points)
{
	TColgp_Array1OfPnt2d pointsA(1, points.size());
	for (int i = 0; i < points.size(); ++i)
	{
		const auto& cp = points[i];
		pointsA.SetValue(i + 1, gp_Pnt2d(cp.x(), cp.y()));
	}
	return pointsA;
}

std::vector<Handle(Geom2d_Curve)> Spline2D::getCurve2d() const
{
	// 计算m_points
	int cpNum = d_ptr->m_ControlPoints.size();
	TColgp_Array1OfPnt2d ctrlPoints = convert2D(d_ptr->m_ControlPoints);
	d_ptr->m_Weights.resize(cpNum, 1.0);
	TColStd_Array1OfReal weights = convert(d_ptr->m_Weights);
	std::vector<double> kv;
	std::vector<int> mv;
	toOCCTKnots(d_ptr->m_Knots, kv, mv);
	TColStd_Array1OfReal knots = convert(kv);
	TColStd_Array1OfInteger mults = convert(mv);
	Handle(Geom2d_BSplineCurve) curve = new Geom2d_BSplineCurve(ctrlPoints, weights, knots, mults, d_ptr->m_Degree);
	if (d_ptr->m_Flag & 2) //周期性样条曲线
		curve->SetPeriodic();
	// 封闭曲线 ?
	std::vector<Handle(Geom2d_Curve)> result;
	result.push_back(curve);
	return result;
}

std::vector<std::shared_ptr<Ent2DCurve>> Spline2D::trim(const SectionParam& from, const SectionParam& to) const
{
	std::vector<std::shared_ptr<Ent2DCurve>> result;
	auto curve2ds = getCurve2d();
	SectionParam start, end;
	getStartEndParam(curve2ds, start, end);
	if (start < from)
	{
		Handle(Geom2d_TrimmedCurve) newCurve = new Geom2d_TrimmedCurve(curve2ds[0], start.param, from.param);
		Handle(Geom2d_BSplineCurve) newSpline = Geom2dConvert::CurveToBSplineCurve(newCurve);
		auto cloneEnt = std::static_pointer_cast<Spline2D>(clone());
		fromCurve(newSpline, cloneEnt->d_ptr->m_Flag, cloneEnt->d_ptr->m_Degree, cloneEnt->d_ptr->m_Knots, cloneEnt->d_ptr->m_Weights, cloneEnt->d_ptr->m_ControlPoints);
		result.push_back(cloneEnt);
	}
	if (to < end)
	{
		Handle(Geom2d_TrimmedCurve) newCurve = new Geom2d_TrimmedCurve(curve2ds[0], to.param, end.param);
		Handle(Geom2d_BSplineCurve) newSpline = Geom2dConvert::CurveToBSplineCurve(newCurve);
		auto cloneEnt = std::static_pointer_cast<Spline2D>(clone());
		fromCurve(newSpline, cloneEnt->d_ptr->m_Flag, cloneEnt->d_ptr->m_Degree, cloneEnt->d_ptr->m_Knots, cloneEnt->d_ptr->m_Weights, cloneEnt->d_ptr->m_ControlPoints);
		result.push_back(cloneEnt);
	}
	return result;
}

std::shared_ptr<Spline3D> makeFitCurve(INode* parent, const std::vector<osg::Vec3d>& points);

std::vector<std::shared_ptr<Ent3DCurve>> Spline2D::to3d()
{
	std::vector<std::shared_ptr<Ent3DCurve>> result;
	UCS* ucs = static_cast<UCS*>(getParent());
	auto matrix = ucs->getMatrix();
	std::vector <osg::Vec3d> points;
	for (auto pt : d_ptr->m_ControlPoints)
	{
		osg::Vec3d point = osg::Vec3d(pt.x(), pt.y(), 0);
		point = point * matrix;
		points.push_back(point);
	}
	Scene* scene = static_cast<Scene*>(getRoot());
	auto spline = std::make_shared<Spline3D>(scene);
	spline->setFlag(d_ptr->m_Flag);
	spline->setDegree(d_ptr->m_Degree);
	spline->setKnots(d_ptr->m_Knots);
	spline->setWeights(d_ptr->m_Weights);
	spline->setCtrlPoints(points);
	result.push_back(spline);
	return result;
}

bool Spline2D::offset(double offset)
{
	auto curves = getCurve2d();
	auto curve = curves[0];
	Handle(Geom2d_OffsetCurve) offsetCurve;
	offsetCurve = new Geom2d_OffsetCurve(curve, offset, true);
	Handle(Geom2d_BSplineCurve) BSplineCurve = Geom2dConvert::CurveToBSplineCurve(offsetCurve);
	int flag, degree;
	std::vector<double> knots, weights;
	std::vector<osg::Vec3d> controlPoints;
	fromCurve(BSplineCurve, flag, degree, knots, weights, controlPoints);
	setFlag(flag);
	setDegree(degree);
	setKnots(knots);
	setWeights(weights);
	setCtrlPoints(controlPoints);
	return true;
}

bool Spline2D::offset(double offset, const osg::Vec3d & offsetSide)
{
	auto curves = getCurve2d();
	auto curve = curves[0];
	Geom2dAPI_ProjectPointOnCurve proj;
	gp_Pnt2d offsetPnt(offsetSide.x(), offsetSide.y());
	proj.Init(offsetPnt, curve);
	gp_Pnt2d projPt;
	if (proj.NbPoints() > 0)
	{
		projPt = proj.NearestPoint();
	}
	gp_Vec2d projOffsetVec(projPt, offsetPnt);
	SectionParam param;
	bool success = getSectionParam(curves, projPt, param);
	if (!success)
		return false;
	gp_Pnt2d pt;
	gp_Vec2d projPtVec;
	curve->D1(param.param, pt, projPtVec);
	double V = projPtVec ^ projOffsetVec;
	if (V > 0)
		offset = -offset;
	return Spline2D::offset(offset);
}
